package com.zh.common.view

import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Bitmap
import android.graphics.ColorFilter
import android.graphics.Shader.TileMode
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.net.Uri
import android.util.AttributeSet
import android.util.Log
import androidx.annotation.ColorInt
import androidx.annotation.DimenRes
import androidx.annotation.DrawableRes
import androidx.appcompat.widget.AppCompatImageView
import com.zh.common.R
import com.zh.common.view.makeramen.Corner
import com.zh.common.view.makeramen.RoundedDrawable

/**
 * @auth zh
 * @time 2022/11/11 15:11
 * @desc 圆角图片ImageView
 */
class RoundedImageView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyle: Int = 0
) : AppCompatImageView(context, attrs, defStyle) {

    // Constants for tile mode attributes
    private val TILE_MODE_UNDEFINED = -2
    private val TILE_MODE_CLAMP = 0
    private val TILE_MODE_REPEAT = 1
    private val TILE_MODE_MIRROR = 2

    private val TAG = "RoundedImageView"
    private val DEFAULT_RADIUS = 0f
    private val DEFAULT_BORDER_WIDTH = 0f
    private val DEFAULT_TILE_MODE = TileMode.CLAMP
    private val SCALE_TYPES = arrayOf(
        ScaleType.MATRIX,
        ScaleType.FIT_XY,
        ScaleType.FIT_START,
        ScaleType.FIT_CENTER,
        ScaleType.FIT_END,
        ScaleType.CENTER,
        ScaleType.CENTER_CROP,
        ScaleType.CENTER_INSIDE
    )

    private val mCornerRadii =
        floatArrayOf(DEFAULT_RADIUS, DEFAULT_RADIUS, DEFAULT_RADIUS, DEFAULT_RADIUS)

    private var mBackgroundDrawable: Drawable? = null
    private var mBorderColor = ColorStateList.valueOf(RoundedDrawable.DEFAULT_BORDER_COLOR)
    private var mBorderWidth = DEFAULT_BORDER_WIDTH
    private var mColorFilter: ColorFilter? = null
    private var mColorMod = false
    private var mDrawable: Drawable? = null
    private var mHasColorFilter = false
    private var mIsOval = false
    private var mMutateBackground = false
    private var mResource = 0
    private var mBackgroundResource = 0
    private var mScaleType: ScaleType? = null
    private var mTileModeX = this.DEFAULT_TILE_MODE
    private var mTileModeY = this.DEFAULT_TILE_MODE

    init {
        val a = context.obtainStyledAttributes(attrs, R.styleable.RoundedImageView, defStyle, 0)
        val index = a.getInt(R.styleable.RoundedImageView_android_scaleType, -1)
        scaleType = if (index >= 0) {
            SCALE_TYPES[index]
        } else {
            // default scaletype to FIT_CENTER
            ScaleType.FIT_CENTER
        }
        var cornerRadiusOverride =
            a.getDimensionPixelSize(R.styleable.RoundedImageView_riv_corner_radius, -1)
                .toFloat()
        mCornerRadii[Corner.TOP_LEFT] =
            a.getDimensionPixelSize(R.styleable.RoundedImageView_riv_corner_radius_top_left, -1)
                .toFloat()
        mCornerRadii[Corner.TOP_RIGHT] = a.getDimensionPixelSize(
            R.styleable.RoundedImageView_riv_corner_radius_top_right,
            -1
        ).toFloat()
        mCornerRadii[Corner.BOTTOM_RIGHT] = a.getDimensionPixelSize(
            R.styleable.RoundedImageView_riv_corner_radius_bottom_right,
            -1
        ).toFloat()
        mCornerRadii[Corner.BOTTOM_LEFT] = a.getDimensionPixelSize(
            R.styleable.RoundedImageView_riv_corner_radius_bottom_left,
            -1
        ).toFloat()
        var any = false
        var i = 0
        val len = mCornerRadii.size
        while (i < len) {
            if (mCornerRadii[i] < 0) {
                mCornerRadii[i] = 0f
            } else {
                any = true
            }
            i++
        }
        if (!any) {
            if (cornerRadiusOverride < 0) {
                cornerRadiusOverride = DEFAULT_RADIUS
            }
            var i = 0
            val len = mCornerRadii.size
            while (i < len) {
                mCornerRadii[i] = cornerRadiusOverride
                i++
            }
        }
        mBorderWidth =
            a.getDimensionPixelSize(R.styleable.RoundedImageView_riv_border_width, -1).toFloat()
        if (mBorderWidth < 0) {
            mBorderWidth = DEFAULT_BORDER_WIDTH
        }
        mBorderColor = if (a.hasValue(R.styleable.RoundedImageView_riv_border_color)) {
            a.getColorStateList(R.styleable.RoundedImageView_riv_border_color)!!
        } else {
            ColorStateList.valueOf(RoundedDrawable.DEFAULT_BORDER_COLOR)
        }
        mMutateBackground =
            a.getBoolean(R.styleable.RoundedImageView_riv_mutate_background, false)
        mIsOval = a.getBoolean(R.styleable.RoundedImageView_riv_oval, false)
        val tileMode = a.getInt(R.styleable.RoundedImageView_riv_tile_mode, TILE_MODE_UNDEFINED)
        if (tileMode != TILE_MODE_UNDEFINED) {
            parseTileMode(tileMode)?.let { setTileModeX(it) }
            parseTileMode(tileMode)?.let { setTileModeY(it) }
        }
        val tileModeX =
            a.getInt(R.styleable.RoundedImageView_riv_tile_mode_x, TILE_MODE_UNDEFINED)
        if (tileModeX != TILE_MODE_UNDEFINED) {
            parseTileMode(tileModeX)?.let { setTileModeX(it) }
        }
        val tileModeY =
            a.getInt(R.styleable.RoundedImageView_riv_tile_mode_y, TILE_MODE_UNDEFINED)
        if (tileModeY != TILE_MODE_UNDEFINED) {
            parseTileMode(tileModeY)?.let { setTileModeY(it) }
        }
        updateDrawableAttrs()
        updateBackgroundDrawableAttrs(true)
        if (mMutateBackground) {
            // when setBackground() is called by View constructor, mMutateBackground is not loaded from the attribute,
            // so it's false by default, what doesn't allow to create the RoundedDrawable. At this point, after load
            // mMutateBackground and updated BackgroundDrawable to RoundedDrawable, the View's background drawable needs to
            // be changed to this new drawable.
            super.setBackgroundDrawable(mBackgroundDrawable)
        }
        a.recycle()
    }

    private fun parseTileMode(tileMode: Int): TileMode? {
        return when (tileMode) {
            TILE_MODE_CLAMP -> TileMode.CLAMP
            TILE_MODE_REPEAT -> TileMode.REPEAT
            TILE_MODE_MIRROR -> TileMode.MIRROR
            else -> null
        }
    }

    override fun drawableStateChanged() {
        super.drawableStateChanged()
        invalidate()
    }

    override fun getScaleType(): ScaleType? {
        return mScaleType
    }

    override fun setScaleType(scaleType: ScaleType?) {
        assert(scaleType != null)
        if (mScaleType != scaleType) {
            mScaleType = scaleType
            when (scaleType) {
                ScaleType.CENTER, ScaleType.CENTER_CROP, ScaleType.CENTER_INSIDE, ScaleType.FIT_CENTER, ScaleType.FIT_START, ScaleType.FIT_END, ScaleType.FIT_XY -> super.setScaleType(
                    ScaleType.FIT_XY
                )

                else -> super.setScaleType(scaleType)
            }
            updateDrawableAttrs()
            updateBackgroundDrawableAttrs(false)
            invalidate()
        }
    }

    override fun setImageDrawable(drawable: Drawable?) {
        mResource = 0
        mDrawable = RoundedDrawable.fromDrawable(drawable)
        updateDrawableAttrs()
        super.setImageDrawable(mDrawable)
    }

    override fun setImageBitmap(bm: Bitmap?) {
        mResource = 0
        mDrawable = RoundedDrawable.fromBitmap(bm)
        updateDrawableAttrs()
        super.setImageDrawable(mDrawable)
    }

    override fun setImageResource(@DrawableRes resId: Int) {
        if (mResource != resId) {
            mResource = resId
            mDrawable = resolveResource()
            updateDrawableAttrs()
            super.setImageDrawable(mDrawable)
        }
    }

    override fun setImageURI(uri: Uri?) {
        super.setImageURI(uri)
        setImageDrawable(drawable)
    }

    private fun resolveResource(): Drawable? {
        val rsrc = resources ?: return null
        var d: Drawable? = null
        if (mResource != 0) {
            try {
                d = rsrc.getDrawable(mResource)
            } catch (e: Exception) {
                Log.w(TAG, "Unable to find resource: $mResource", e)
                // Don't try again.
                mResource = 0
            }
        }
        return RoundedDrawable.fromDrawable(d)
    }

    override fun setBackground(background: Drawable?) {
        setBackgroundDrawable(background)
    }

    override fun setBackgroundResource(@DrawableRes resId: Int) {
        if (mBackgroundResource != resId) {
            mBackgroundResource = resId
            mBackgroundDrawable = resolveBackgroundResource()
            setBackgroundDrawable(mBackgroundDrawable)
        }
    }

    override fun setBackgroundColor(color: Int) {
        mBackgroundDrawable = ColorDrawable(color)
        setBackgroundDrawable(mBackgroundDrawable)
    }

    private fun resolveBackgroundResource(): Drawable? {
        val rsrc = resources ?: return null
        var d: Drawable? = null
        if (mBackgroundResource != 0) {
            try {
                d = rsrc.getDrawable(mBackgroundResource)
            } catch (e: Exception) {
                Log.w(TAG, "Unable to find resource: $mBackgroundResource", e)
                // Don't try again.
                mBackgroundResource = 0
            }
        }
        return RoundedDrawable.fromDrawable(d)
    }

    private fun updateDrawableAttrs() {
        updateAttrs(mDrawable, mScaleType)
    }

    private fun updateBackgroundDrawableAttrs(convert: Boolean) {
        if (mMutateBackground) {
            if (convert) {
                mBackgroundDrawable = RoundedDrawable.fromDrawable(mBackgroundDrawable)
            }
            updateAttrs(mBackgroundDrawable, ScaleType.FIT_XY)
        }
    }

    override fun setColorFilter(cf: ColorFilter) {
        if (mColorFilter !== cf) {
            mColorFilter = cf
            mHasColorFilter = true
            mColorMod = true
            applyColorMod()
            invalidate()
        }
    }

    private fun applyColorMod() {
        // Only mutate and apply when modifications have occurred. This should
        // not reset the mColorMod flag, since these filters need to be
        // re-applied if the Drawable is changed.
        if (mDrawable != null && mColorMod) {
            mDrawable = mDrawable?.mutate()
            if (mHasColorFilter) {
                mDrawable?.colorFilter = mColorFilter
            }
            // TODO: support, eventually...
            //mDrawable.setXfermode(mXfermode);
            //mDrawable.setAlpha(mAlpha * mViewAlphaScale >> 8);
        }
    }

    private fun updateAttrs(drawable: Drawable?, scaleType: ScaleType?) {
        if (drawable == null) {
            return
        }
        if (drawable is RoundedDrawable) {
            (drawable as RoundedDrawable)
                .setScaleType(scaleType)
                .setBorderWidth(mBorderWidth)
                .setBorderColor(mBorderColor)
                .setOval(mIsOval)
                .setTileModeX(DEFAULT_TILE_MODE).tileModeY = DEFAULT_TILE_MODE
            if (mCornerRadii != null) {
                drawable.setCornerRadius(
                    mCornerRadii[Corner.TOP_LEFT],
                    mCornerRadii[Corner.TOP_RIGHT],
                    mCornerRadii[Corner.BOTTOM_RIGHT],
                    mCornerRadii[Corner.BOTTOM_LEFT]
                )
            }
            applyColorMod()
        } else if (drawable is LayerDrawable) {
            // loop through layers to and set drawable attrs
            val ld = drawable as LayerDrawable
            var i = 0
            val layers = ld.numberOfLayers
            while (i < layers) {
                updateAttrs(ld.getDrawable(i), scaleType)
                i++
            }
        }
    }

    @Deprecated("")
    override fun setBackgroundDrawable(background: Drawable?) {
        mBackgroundDrawable = background
        updateBackgroundDrawableAttrs(true)
        super.setBackgroundDrawable(mBackgroundDrawable)
    }

    /**
     * @return the largest corner radius.
     */
    fun getCornerRadius(): Float {
        return getMaxCornerRadius()
    }

    /**
     * @return the largest corner radius.
     */
    fun getMaxCornerRadius(): Float {
        var maxRadius = 0f
        for (r in mCornerRadii) {
            maxRadius = Math.max(r, maxRadius)
        }
        return maxRadius
    }

    /**
     * Get the corner radius of a specified corner.
     *
     * @param corner the corner.
     * @return the radius.
     */
    fun getCornerRadius(@Corner corner: Int): Float {
        return mCornerRadii[corner]
    }

    /**
     * Set all the corner radii from a dimension resource id.
     *
     * @param resId dimension resource id of radii.
     */
    fun setCornerRadiusDimen(@DimenRes resId: Int) {
        val radius = resources.getDimension(resId)
        setCornerRadius(radius, radius, radius, radius)
    }

    /**
     * Set the corner radius of a specific corner from a dimension resource id.
     *
     * @param corner the corner to set.
     * @param resId the dimension resource id of the corner radius.
     */
    fun setCornerRadiusDimen(@Corner corner: Int, @DimenRes resId: Int) {
        setCornerRadius(corner, resources.getDimensionPixelSize(resId).toFloat())
    }

    /**
     * Set the corner radii of all corners in px.
     *
     * @param radius the radius to set.
     */
    fun setCornerRadius(radius: Float) {
        setCornerRadius(radius, radius, radius, radius)
    }

    /**
     * Set the corner radius of a specific corner in px.
     *
     * @param corner the corner to set.
     * @param radius the corner radius to set in px.
     */
    fun setCornerRadius(@Corner corner: Int, radius: Float) {
        if (mCornerRadii[corner] == radius) {
            return
        }
        mCornerRadii[corner] = radius
        updateDrawableAttrs()
        updateBackgroundDrawableAttrs(false)
        invalidate()
    }

    /**
     * Set the corner radii of each corner individually. Currently only one unique nonzero value is
     * supported.
     *
     * @param topLeft radius of the top left corner in px.
     * @param topRight radius of the top right corner in px.
     * @param bottomRight radius of the bottom right corner in px.
     * @param bottomLeft radius of the bottom left corner in px.
     */
    fun setCornerRadius(
        topLeft: Float,
        topRight: Float,
        bottomLeft: Float,
        bottomRight: Float
    ) {
        if (mCornerRadii[Corner.TOP_LEFT] == topLeft && mCornerRadii[Corner.TOP_RIGHT] == topRight && mCornerRadii[Corner.BOTTOM_RIGHT] == bottomRight && mCornerRadii[Corner.BOTTOM_LEFT] == bottomLeft) {
            return
        }
        mCornerRadii[Corner.TOP_LEFT] = topLeft
        mCornerRadii[Corner.TOP_RIGHT] = topRight
        mCornerRadii[Corner.BOTTOM_LEFT] = bottomLeft
        mCornerRadii[Corner.BOTTOM_RIGHT] = bottomRight
        updateDrawableAttrs()
        updateBackgroundDrawableAttrs(false)
        invalidate()
    }

    fun getBorderWidth(): Float {
        return mBorderWidth
    }

    fun setBorderWidth(@DimenRes resId: Int) {
        setBorderWidth(resources.getDimension(resId).toInt())
    }

    fun setBorderWidth(width: Float) {
        if (mBorderWidth == width) {
            return
        }
        mBorderWidth = width
        updateDrawableAttrs()
        updateBackgroundDrawableAttrs(false)
        invalidate()
    }

    @ColorInt
    fun getBorderColor(): Int {
        return mBorderColor.defaultColor
    }

    fun setBorderColor(@ColorInt color: Int) {
        setBorderColor(ColorStateList.valueOf(color))
    }

    fun getBorderColors(): ColorStateList? {
        return mBorderColor
    }

    fun setBorderColor(colors: ColorStateList?) {
        if (mBorderColor == colors) {
            return
        }
        mBorderColor = colors ?: ColorStateList.valueOf(RoundedDrawable.DEFAULT_BORDER_COLOR)
        updateDrawableAttrs()
        updateBackgroundDrawableAttrs(false)
        if (mBorderWidth > 0) {
            invalidate()
        }
    }

    fun isOval(): Boolean {
        return mIsOval
    }

    fun setOval(oval: Boolean) {
        mIsOval = oval
        updateDrawableAttrs()
        updateBackgroundDrawableAttrs(false)
        invalidate()
    }

    fun getTileModeX(): TileMode? {
        return DEFAULT_TILE_MODE
    }

    fun setTileModeX(tileModeX: TileMode) {
        if (this.mTileModeX == tileModeX) {
            return
        }
        this.mTileModeX = tileModeX
        updateDrawableAttrs()
        updateBackgroundDrawableAttrs(false)
        invalidate()
    }

    fun getTileModeY(): TileMode? {
        return DEFAULT_TILE_MODE
    }

    fun setTileModeY(tileModeY: TileMode) {
        if (this.mTileModeY == tileModeY) {
            return
        }
        this.mTileModeY = tileModeY
        updateDrawableAttrs()
        updateBackgroundDrawableAttrs(false)
        invalidate()
    }

    fun mutatesBackground(): Boolean {
        return mMutateBackground
    }

    fun mutateBackground(mutate: Boolean) {
        if (mMutateBackground == mutate) {
            return
        }
        mMutateBackground = mutate
        updateBackgroundDrawableAttrs(true)
        invalidate()
    }
}